﻿using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
using CoreEx.Common.Logging;
using CoreEx.DynamicInvoker;
using LinFu.AOP.Interfaces;

namespace CoreEx.ServiceModel.Implementation
{
    /// <summary>
    /// Intercepts calls made to a service contract and handles 
    /// the creation of <see cref="IChannel"/> instances as well as 
    /// properly closing the channels after method calls have been made.
    /// </summary>
    public class ServiceContractClientInterceptor<TChannel> : IInterceptor
    {
        private readonly ChannelFactory<TChannel> _channelFactory;

        /// <summary>
        /// Initializes a new instance of the <see cref="ServiceContractClientInterceptor{TChannel}"/> class.
        /// </summary>
        /// <param name="channelFactory">The <see cref="IChannelFactory{TService}"/> instance that 
        /// is responsible for creating new channels</param>
        public ServiceContractClientInterceptor(ChannelFactory channelFactory)
        {
            _channelFactory = (ChannelFactory<TChannel>)channelFactory;
        }


        /// <summary>
        /// Intercepts a method call made to service contract.
        /// </summary>
        /// <param name="info">The information associated with the method call.</param>
        /// <returns><see cref="object"/></returns>
        public object Intercept(IInvocationInfo info)
        {
            TChannel channel = CreateChannel();
            object returnValue = InvokeTargetMethod(info, channel);
            Close((ICommunicationObject)channel);
            return returnValue;
        }

        private object InvokeTargetMethod(IInvocationInfo info, TChannel channel)
        {
            this.LogDebug(string.Format("(Client) Start invoke method: {0}", info.TargetMethod));
            object returnValue =  info.TargetMethod.DynamicInvoke(channel, info.Arguments);
            this.LogDebug(string.Format("(Client) Finished invoking method: {0}", info.TargetMethod));
            return returnValue;
        }

        private TChannel CreateChannel()
        {
            this.LogDebug(string.Format("(Client) Creating channel ({0})", typeof(TChannel).FullName));
            return _channelFactory.CreateChannel();
        }

        private void Close(ICommunicationObject communicationObject)
        {
            this.LogDebug(string.Format("(Client) Closing channel ({0})", typeof(TChannel).FullName));
            // do not throw null reference exception
            if (communicationObject == null)
            {
                return;
            }
            if (communicationObject.State != CommunicationState.Closed
                && communicationObject.State != CommunicationState.Closing)
            {
                try
                {
                    // if Close attempts closing the session on the server and there
                    // is a problem during the roundtrip it will throw an exception
                    // and never transition to the Closed state
                    communicationObject.Close();
                }
                catch (CommunicationException)
                {
                    // not closed - call Abort to transition to the closed state
                    communicationObject.Abort();
                }
                catch (TimeoutException)
                {
                    // not closed - call Abort to transition to the closed state
                    communicationObject.Abort();
                }
                catch (Exception)
                {
                    // not closed - call Abort to transition to the closed state
                    communicationObject.Abort();
                    // this is an unexpected exception type - throw
                    throw;
                }
            }
        }

    }
}